Repository navigation
docs: add the module and versioning design - #147
Merged
Merged
Conversation
Records the decisions taken so far for modules, imports and versioning, none of them implemented yet: no visibility, so everything a module defines is part of its contract; the major version in the module path, so majors coexist; SemVer categories enforced when publishing, from what declarations state rather than from tested argument types; the publishing steps, including rerunning the previous release's tests and requiring every function body to run in the module's tests; deprecation; and development reports that keep compilation permissive. Lists the questions still open. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A failing test from the previous release stops publishing only when nothing explains it: a change the comparison found, a declared break or bug fix, or something outside the contract such as a struct's printed form. Before, any failure stopped publishing, which ruled out every Major release that removes a function. - A struct constant keeps its value while every field it had keeps its type and value, so a Minor field addition no longer counts as changing the constants of that struct type. Reordering fields is Major. - Distinct struct types across majors don't stop a template from accepting either one. - Rerun tests catch only what their assertions or type-sensitive operations expose, and a narrowing that escapes is a breaking change the release should have declared. - The visibility rationale is that functions share no mutable state; functions can print and read constants, so the stronger claims are gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Minor release no longer marks things as going away, so compiling never warns about what a later Major release will remove. A Major release may remove or change anything; each dependent moves to it when it chooses, since majors coexist, and the release notes list every removal and change, taken from the comparison with the release before it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… order The comparison only sees declarations, so a behavior change such as `Double(x)` going from `x * 2` to `x * 3` never shows up in it. Release notes now list the removals and changes the comparison detects together with the breaking changes the publisher declares and their migration steps, and a module's own documentation can give advance notice of removals. The API manifest records each struct's ordered field names and types, since reordering fields is Major and the checker has to see it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The publisher no longer proposes a version for the checker to accept or reject. Publishing works out the category from the detected changes and any declared break, and numbers the release itself; the publisher may ask for a higher category, never a lower one. Publishing reports the version it picked and why, and a request for a lower category is refused with the change that rules it out. Adds an open question: how a publisher declares a break or a bug fix and asks for a higher category. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Publishing worked out a release's category from detected changes and declared breaks only, so a template that newly accepts more argument types, which the comparison cannot see, would have been numbered as a Patch although the table requires a Minor. The publisher now declares compatible additions the comparison cannot see as well as breaks, and the category is the highest any detected or declared change requires. Also states that release notes are generated, so publishers never write out changed functions, constants or fields; that requiring every function body to run shows each function runs, not that it is needed; and adds backports to the open questions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Records the decisions taken in review for how modules use each other, which the design had left as open questions: - There are no import statements. A module lists its dependencies in pt.mod, each with the minimum release it needs and an optional alias, and names the oldest compiler it builds with. - The standard library ships with the compiler as a set of libraries: int, float, str, array, complex and math are aliased as . by default and can be re-aliased in pt.mod, and there is no std prefix. - A name and input count may be defined once among a module's own functions, the standard libraries aliased as . and dependencies aliased as .; a second definition is an error, never a shadow. A module sees standard names as of its minimum compiler, and only an unpublished program root may alias a dependency as ., since a Minor release of it could otherwise break builds that cannot fix the clash. - Releases are chosen by minimal version selection, as in Go, with no lock file. Published releases never change, so the same root pt.mod selects the same releases, while the binary also depends on the source, compiler and build settings. Compiled code is cached by everything it was compiled from, every release it depends on transitively included, since a struct reached through an unchanged dependency still changes what a module compiles to. - Symbols carry the selected release. Struct types in symbols still carry only their name (Struct.Mangle), so telling a v1 struct from a v2 one is future work. - pt.sum, committed beside pt.mod, verifies dependency manifests and sources without selecting versions. Releases are tags in each module's own repository, as in Go, with an optional proxy that keeps copies and an optional checksum log that rechecks each release's version number against its API changes before recording it. - C callers reach a module through wrappers whose symbols keep the major, and which hand out structs only as opaque handles, so a struct can still gain a field in a Minor release. - A directory without a pt.mod compiles as a standalone program with the compiler's standard library; today the compiler stops without one. The open questions now cover how pt.mod spells these, who runs the proxy and the log, whether raising the minimum compiler is Minor or Major, what 0.y.z releases promise and what module path a standalone program uses. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fills gaps a review found in the module design: - Names without a prefix are checked in the compiler's three namespaces: functions by name and number of inputs, constants by name, a struct's constants included, and struct types by name. A constant Pi and a function Pi(x) may coexist, as they already can within a module, and a module's constant of a struct type defined elsewhere uses that type. Prefixes form a namespace of their own. - A module's minimum compiler hides later standard libraries, functions at each number of inputs, constants and struct types, so a new standard library cannot take a default alias a module already uses. - There is no implicit std namespace: std.Int is an error unless a module gives a library the alias std. - Min and Max take ordered numbers only. - A private module's first checksum is checked against a log its owner runs, or taken from the first authenticated download, as Go does for modules outside its checksum database; a later mismatch still fails. - The checksum log compares a release with the highest release below it in its major line, from its own copy, and checks only the version number against the detected API changes, not tests, coverage or what a publisher would declare. - What a conversion does with unreadable text or a value it cannot hold joins the open questions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds
docs/Pluto Module Design.md, a design record for modules, dependencies and versioning. Nothing in it is implemented yet: today a directory is the unit of compilation,pt.modnames the module path, and a module cannot use another.What it records
pt.mod, with no import statements: each dependency with the minimum release it needs and an optional alias, usable from every.ptand.sptfile.pt.modalso names the oldest compiler the module builds with.int,float,str,array,complexandmathare aliased as.by default and can be re-aliased inpt.mod, so code writesInt(23.2), and there is no implicitstdnamespace. A function that serves several types lives in one library, such asmath'sAbs, for the argument types it supports:MinandMaxtake ordered numbers only..and dependencies aliased as., is an error. Prefixes are a namespace too. A module sees standard libraries, functions, constants and struct types as of its minimum compiler, so a later library takes no default alias either, and only an unpublished program root may alias a dependency as..Struct.Mangle), so that distinction is still to be built.pt.modselects the same releases; the binary also depends on the source, compiler, platform and build settings. Compiled code is cached by everything it was compiled from, every release it depends on transitively included.pt.sumand release records:pt.sumverifies dependency manifests and sources and never selects a version, and a mismatch fails the build. A release's record is its checksum-log entry with the proxy's copy; a Git tag alone cannot vouch for contents. A private module's first checksum is checked against a log its owner runs, or taken from the first authenticated download.pt.mod.pt.modspells dependencies, aliases and the minimum compiler, and how code names a dependency's function; who runs the proxy and the checksum log, and how a private module opts out; whether raising the minimum compiler is Minor or Major; what a conversion does with text it cannot read or a value it cannot hold; what module path a standalone program uses; how a publisher declares an addition, break or bug fix and asks for a higher category; backports; whether the major goes in the path or only in the version; what0.y.zreleases promise; which functions get a stable C wrapper; and an inferred check of what each input must support.Commits
docs:the design record.docs:from review: a failing test from the previous release must be explained rather than always stopping publishing; struct constants and Minor field additions; field order; generic calls across majors; what rerun tests catch; the visibility rationale.docs:drops deprecation marks, so compiling never warns about future removals.docs:from review: release notes include declared breaks and migration steps, and the manifest keeps struct field order.docs:publishing picks the version number, so a publisher never chooses one and can only ask for a higher category.docs:from review: publishers declare compatible additions the comparison cannot see (Minor) as well as breaks (Major); release notes are generated; running every function shows it runs, not that it is needed; backports join the open questions.docs:settles the open questions on dependencies and releases:pt.modwithout imports, the standard library and its.aliases, no shadowing, minimal version selection with a transitive cache key, versions in symbols,pt.sum, release records and hosting, C callers and standalone programs.docs:from review: collision rules for each kind of name and for prefixes; minimum-compiler visibility for libraries, constants and struct types; no implicitstdnamespace;MinandMaxfor ordered numbers; private first use; what the checksum log checks and against which release; conversions join the open questions.Verification
go vetandgo test -race ./lexer ./parser ./compilerpass, andpython3 test.py --leak-checkpasses 84/84 with every leak check clean, at d7f23f5; the seventh and eighth commits change only the doc.tests/structprintsAda 28 0), a template is keyed by name and input count, versioned module paths are valid and mangled (C ABI spec §1.1 and §4.3), and[Double(0:0)]never runsDouble's body.Function Twice with 1 parameters has been previously defined at /a.pt:1:7);Struct.Mangle(compiler/types.go:485) mangles a struct by its name alone; return types are not mangled (C ABI spec §3.4);nativeandportablePLUTO_TARGET_CPUbuilds are cached apart; and with nopt.modabove it, the compiler stops withpt.mod not found ... upward.Piand a functionPi(x)compile together in one module, as do a constant or a function named after a struct type, so functions, constants and struct types are separate namespaces today.git merge-treeof the two is clean.🤖 Generated with Claude Code